/*日志器模块：
    功能：对前边所有模块进行整合，向外提供接口完成不同等级日志的输出
    管理的成员：
      1、格式化模块对象
      2、落地模块对象数组（一个日志器可能会向多个位置进行日志输出）
      3、默认的输出限制等级（大于等于限制等级的日志才能输出）
      4、互斥锁（保证日志输出过程是线程安全的，不会出现交叉日志）
      5、日志器名称（日志器的唯一标识，以便于查找）
    提供的操作：
      debug等级日志的输出操作（分别会封装日志消息LogMsg--各个接口日志等级不同）
      info等级日志的输出操作
      warn等级日志的输出操作
      error等级日志的输出操作
      fatal等级日志的输出操作
    实现：
      1、抽象Logger基类（派生出同步日志器类&异步日志器类）
      2、因为两种不同的日志器，只有落地方式不同，因此将落地操作给抽象出来
         不同的日志器调用各自的落地操作进行日志落地
      模块关联中使用基类指针对子类日志器对象进行日志管理和操作
*/

/*异步日志器设计：
    1、继承于Logger日志器类
       对于写日志操作进行函数重写（不再将数据直接写入文件，而是通过异步消息处理器，放到缓冲区中）
    2、通过异步消息处理器，进行日志数据的实际落地
  管理成员：
    1、异步工作器（异步消息处理器）
  完成后，完善日志器建造者，进行异步日志器安全模式的选择，提供异步日志器的创建
*/

/*完成日志器模块：
  1、抽象日志器基类
  2、派生出不同的子类（同步日志器类 & 异步日志器类）
*/
#ifndef __M_LOGGER_H__
#define __M_LOGGER_H__
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "util.hpp"
#include "level.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "looper.hpp"
#include "bitlog.h"
#include <atomic>
#include <mutex>
#include <cstdarg>
#include <unordered_map>
namespace bitlog
{
    class Logger
    {
    public:
        using ptr = std::shared_ptr<Logger>;
        Logger(const std::string &logger_name,
               LogLevel::value level,
               Formatter::ptr &formatter,
               std::vector<LogSink::ptr> &sinks)
            : _logger_name(logger_name),
              _limit_level(level),
              _formatter(formatter),
              _sinks(sinks.begin(), sinks.end())
        {
        }
        // 获取日志器名称，避免外界修改，用const引用操作
        const std::string &name()
        {
            return _logger_name;
        }
        // 完成构造日志消息对象过程并进行格式化，得到格式化后的日志消息字符串--然后进行落地输出
        // debug的输出日志，必须传入（行号、文件名），等级就是默认等级dubug、、有效的消息数据不需要用户自己组织，所以这些接口都是不定参函数
        // 根据fmt来取出后面的不定参数据，进行处理
        void debug(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通风传入的参数构造出一个日志消息对象进行日志的格式化，最终落地
            // 1、判断当前的日志是否达到了输出等级
            if (LogLevel::value::DEBUG < _limit_level)
            {
                return;
            }
            // 2、对fmt格式化字符串和不定参进行字符串组织，得到的日志消息的字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf failed!!\n";
                return;
            }
            va_end(ap); // 将ap指针置空
            serialize(LogLevel::value::DEBUG, file, line, res);

            free(res); // 不释放就会发送空间泄漏，因为vasprintf里组织字符串会申请一个空间，然后放到一个空间里去，将空间地址赋给res，所以res的空间是动态申请的，要free
        }
        void info(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通风传入的参数构造出一个日志消息对象进行日志的格式化，最终落地
            // 1、判断当前的日志是否达到了输出等级
            if (LogLevel::value::INFO < _limit_level)
            {
                return;
            }
            // 2、对fmt格式化字符串和不定参进行字符串组织，得到的日志消息的字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf failed!!\n";
                return;
            }
            va_end(ap); // 将ap指针置空
            serialize(LogLevel::value::INFO, file, line, res);

            free(res);
        }
        void warn(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通风传入的参数构造出一个日志消息对象进行日志的格式化，最终落地
            // 1、判断当前的日志是否达到了输出等级
            if (LogLevel::value::WARN < _limit_level)
            {
                return;
            }
            // 2、对fmt格式化字符串和不定参进行字符串组织，得到的日志消息的字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf failed!!\n";
                return;
            }
            va_end(ap); // 将ap指针置空
            serialize(LogLevel::value::WARN, file, line, res);

            free(res);
        }
        void error(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通风传入的参数构造出一个日志消息对象进行日志的格式化，最终落地
            // 1、判断当前的日志是否达到了输出等级
            if (LogLevel::value::ERROR < _limit_level)
            {
                return;
            }
            // 2、对fmt格式化字符串和不定参进行字符串组织，得到的日志消息的字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf failed!!\n";
                return;
            }
            va_end(ap); // 将ap指针置空
            serialize(LogLevel::value::ERROR, file, line, res);

            free(res);
        }
        void fatal(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通风传入的参数构造出一个日志消息对象进行日志的格式化，最终落地
            // 1、判断当前的日志是否达到了输出等级
            if (LogLevel::value::FATAL < _limit_level)
            {
                return;
            }
            // 2、对fmt格式化字符串和不定参进行字符串组织，得到的日志消息的字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == -1)
            {
                std::cout << "vasprintf failed!!\n";
                return;
            }
            va_end(ap); // 将ap指针置空
            serialize(LogLevel::value::FATAL, file, line, res);

            free(res);
        }

    protected:
        void serialize(LogLevel::value level, const std::string &file, size_t line, char *str)
        {
            // 3、构造LogMsg对象
            LogMsg msg(level, line, file, _logger_name, str);
            // 4、通过格式化工具对LogMsg进行格式化，得到格式化后的日志字符串
            std::stringstream ss;
            _formatter->format(ss, msg); // 格式化完成的数据放在了ss中

            // 5、进行日志落地
            log(ss.str().c_str(), ss.str().size());
        }
        // 抽象接口完成实际的落地输出--不同的日志器会有不同的实际落地方式
        virtual void log(const char *data, size_t len) = 0;

    protected:
        std::mutex _mutex; // 日志落地的时候，要保证线程的安全，所以加一个互斥锁

        std::string _logger_name; // 日志器名称
        // 限制等级，会频繁访问，每一次输出的时候，都要去访问，判断当前这个日志能不能输出，加锁保护的话，锁冲突就比较多，本质是一个整形，所以定义成原子类型的成员，保证多线程原子访问
        std::atomic<LogLevel::value> _limit_level; // 限制等级

        Formatter::ptr _formatter; // 格式化模块的对象

        std::vector<LogSink::ptr> _sinks; // 可能会有多个落地方向
    };
    class SyncLogger : public Logger // 同步日志器
    {
    public:
        SyncLogger(const std::string &logger_name,
                   LogLevel::value level,
                   Formatter::ptr &formatter,
                   std::vector<LogSink::ptr> &sinks)
            : Logger(logger_name, level, formatter, sinks)
        {
        }

    protected:
        // 同步日志器：是将日志直接通过落地模块句柄进行日志落地
        void log(const char *data, size_t len) // 完成实际的落地消息
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_sinks.empty())
                return;
            for (auto &sink : _sinks)
            {
                sink->log(data, len);
            }
        }
    };
    class AsyncLogger : public Logger // 异步日志器
    {
    public:
        AsyncLogger(const std::string &logger_name,
                    LogLevel::value level,
                    Formatter::ptr &formatter,
                    std::vector<LogSink::ptr> &sinks,
                    AsyncType looper_type)
            : Logger(logger_name, level, formatter, sinks),
              _looper(std::make_shared<AsyncLooper>(std::bind(&AsyncLogger::realLog, this, std::placeholders::_1), looper_type))
        // 创建一个AsyncLogger,通过realLog接口生成一个新的可调用对象，新的函数，只需要传入一个参数placeholders::_1，传入一个buffer类型的参数，因为realLog只需要一个参数，而我们传了2个，就绑定进去，外界传入就只需要一个参数
        {
        }
        // 将数据写入缓冲区
        void log(const char *data, size_t len)
        {
            _looper->push(data, len); // 消息入队操作，不需要考虑安全问题，push本身就是线程安全的
        }
        // 设计一个实际落地函数（将缓冲区中的数据落地）
        void realLog(Buffer &buf) // 本身就只有一个，单执行流的落地工程，本身内部就是串行化的过程写入线程，是不需要加锁保护，我们只要保证线程安全的落到缓冲区里_looper->push(data, len)
        {
            if (_sinks.empty())
                return;
            for (auto &sink : _sinks)
            {
                sink->log(buf.begin(), buf.readAbleSize());
            }
        }

    private:
        AsyncLooper::ptr _looper;
    };

    enum class LoggerType
    {
        LOGGER_SYNC, // 同步日志器
        LOGGER_ASYNC // 异步
    };

    /*使用建造者模式来建造日志器，而不要让用户直接去构造日志器，简化用户的使用复杂度*/
    // 1、抽象一个日志器建造者类（完成日志器对象所需零部件的构建 & 日志器的构建）
    // 1.1、设置日志器类型
    // 1.2、将不同类型日志器的创建放当同同一个日志器建造者类中完成
    class LoggerBuilder
    {
    public:
        LoggerBuilder()
            : _logger_type(LoggerType::LOGGER_SYNC), // 同步日志器
              _limit_level(LogLevel::value::DEBUG),  // 默认级别是最低的
              _looper_type(AsyncType::ASYNC_SAFE)    // 缺省是安全状态的
        {
        }
        void buildLoggerType(LoggerType type)
        {
            _logger_type = type;
        }
        void buildEnableUmsafeAsync()
        {
            _looper_type = AsyncType::ASUNC_UNSAFE;
        }
        void buildLoggerName(const std::string &name)
        {
            _logger_name = name;
        }
        void buildLoggerLevel(LogLevel::value level)
        {
            _limit_level = level;
        }
        void buildFormatter(const std::string &pattern) // 构造一个格式化器
        {
            _formatter = std::make_shared<Formatter>(pattern); // 通过智能指针对这个对象进行管理
        }

        template <typename SinkType, typename... Args>
        void buildSink(Args &&...args)
        {
            LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...); // 创建落地器的一个类型,完美转发展开
            _sinks.push_back(psink);                                                         // 将日志器添加到数组里
        }
        virtual Logger::ptr build() = 0; // 日志器对象,建造日志器的
    protected:
        AsyncType _looper_type; // 异步工作模式
        LoggerType _logger_type;
        std::string _logger_name; // 日志器名称,是一个标识，通过这个标识，找到这个日志器

        LogLevel::value _limit_level; // 限制等级,不涉及多线程的操作，不需要原子类型

        Formatter::ptr _formatter; // 格式化模块的对象

        std::vector<LogSink::ptr> _sinks; // 可能会有多个落地方向
    };
    // 2、派生出具体的建造者类 --- 局部日志器的建造者 & 全局的日志器建造者（后面添加了全局单例管理器之后，将日志器添加全局管理）
    class LocalLoggerBuilder : public LoggerBuilder
    {
    public:
        Logger::ptr build() override
        {
            assert(_logger_name.empty() == false); // 必须有日志器名称,不为空就过
            if (_formatter.get() == nullptr)       //==null说明没有日志器
            {
                _formatter = std::make_shared<Formatter>();
            }
            if (_sinks.empty())
            {
                buildSink<StdoutSink>(); // 传入一个类型StdoutSink
            }
            if (_logger_type == LoggerType::LOGGER_ASYNC) // 如果是异步日志器,否则返回同步日志器
            {
                return std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sinks, _looper_type);
            }
            return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
        }
    };

    /*单例日志器管理器：
    作用1：对所以创建的日志器进行管理
    特性：将管理器设计为单例
    作用2：可以在程序的任意位置，获取相同的单例对象，获取其中的日志器进行日志输出
    扩展：单例管理器创建的时候，默认先创建一个日志器（用于进行标准输出的打印）
      目的：让用户在不创建日志日志器的情况下，也能进行标准输出的打印，方便用户使用
    设计：
      管理的成员：
        1、默认日志器
        2、所管理的日志器数组
        3、互斥锁
      提供的接口：
        1、添加日志器管理
        2、判断是否管理了指定名称的日志器
        3、获取指定名称的日志器
        4、获取默认日志器
*/

    class LoggerManager
    {
    public:
        static LoggerManager &getInstance()
        {
            // 在C++11之后，针对静态局部变量，编译器在编译的层面实现了线程安全
            // 当静态局部变量在没有构造完成之前，其他的线程进入就会阻塞
            static LoggerManager eton;
            return eton;
        }

        void addLogger(Logger::ptr &logger) // 添加一个日志器
        {
            if (hasLogger(logger->name())) // 如果不存在，再走下来，进行添加
                return;
            std::unique_lock<std::mutex> lock(_mutex);
            _loggers.insert(std::make_pair(logger->name(), logger));
        }
        bool hasLogger(const std::string &name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _loggers.find(name);
            if (it == _loggers.end())
            {
                return false;
            }
            return true;
        }
        Logger::ptr getLogger(const std::string &name) // 获取日志器
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _loggers.find(name);
            if (it == _loggers.end())
            {
                return Logger::ptr();
            }
            return it->second;
        }
        Logger::ptr rootLogger() // 获取默认日志器
        {
            return _root_logger;
        }

    private:
        LoggerManager()
        {
            std::unique_ptr<bitlog::LoggerBuilder> builder(new bitlog::LocalLoggerBuilder()); // 本地日志的建造者,只能创建一个局部的 ，改成GlobalLoggerBuilder，会被卡住
            // 在单例内部构造对象的时候，内部还没构造完的时候，又去建造一个日志器_root_logger = builder->build();，又因为build()里又涉及到再去构造对象，程序就卡住了
            builder->buildLoggerName("root");                      // 其他都是自动给的
            _root_logger = builder->build();                       // 创建一个日志器出来
            _loggers.insert(std::make_pair("root", _root_logger)); // 添加到日志器当中
        }

    private:
        std::mutex _mutex;
        Logger::ptr _root_logger;                              // 默认日志器
        std::unordered_map<std::string, Logger::ptr> _loggers; // 日志器名称std::string跟日志器对象Logger::ptr的隐射关系
    };

    // 设计一个全局日志器的建造者，在局部的基础上增加了一个功能：将日志器添加到单例对象中。上面是一个本地局部日志器，在哪里创建，哪里使用
    class GlobalLoggerBuilder : public LoggerBuilder
    {
    public:
        Logger::ptr build() override
        {
            assert(_logger_name.empty() == false); // 必须有日志器名称,不为空就过
            if (_formatter.get() == nullptr)       //==nullptr说明没有日志器
            {
                _formatter = std::make_shared<Formatter>();
            }
            if (_sinks.empty())
            {
                buildSink<StdoutSink>(); // 传入一个类型StdoutSink
            }
            Logger::ptr logger;
            if (_logger_type == LoggerType::LOGGER_ASYNC) // 如果是异步日志器,否则返回同步日志器
            {
                logger = std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sinks, _looper_type);
            }
            else
            {
                logger = std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
            }
            LoggerManager::getInstance().addLogger(logger); // 将日志器添加进去
            return logger;
        }
    };
}

#endif